﻿using Couchbase;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GuoZhiQi.MiddleCache.Object.Atomic
{
    public abstract class CouchBaseAtomicInt
        : CouchBaseAtomic<ulong>, ICouchBaseObject
    {
        #region Main value.

        /// <summary>
        /// Value of this parameter.
        /// </summary>
        public override ulong Value
        {
            get;
            set;
        }

        #endregion Main value.

        #region Constructor

        /// <summary>
        /// Constructor of this object.
        /// </summary>
        /// <param name="client">Membase client to work with.</param>
        public CouchBaseAtomicInt(CouchbaseClient client)
            : base(client)
        {
        }

        /// <summary>
        /// Constructor of this object.
        /// </summary>
        /// <param name="client">Membase client to work with.</param>
        /// <param name="objectKey">String key of the object.</param>
        /// <param name="TryLoadFromDB">True if object should be instantly loaded from Membase.</param>
        public CouchBaseAtomicInt(CouchbaseClient client, string objectKey, bool TryLoadFromDB = false)
            : base(client, objectKey, TryLoadFromDB)
        {
            this.Value = 0;
        }

        #endregion Constructor

        #region Atomic functions.

        /// <summary>
        /// Increases value of object and refreshes with new value. Atomic function.
        /// </summary>
        /// <param name="incValue">Ammount for increase.</param>
        public void Increment(int incValue)
        {
            if (this.Key == null)
                throw new NullReferenceException("Key is not set yet.");

            if (this._expiresAfter.HasValue)
                this._client.Touch(this.Key, this._expiresAfter.Value);
            else if (this._expiresOn.HasValue)
                this._client.Touch(this.Key, this._expiresOn.Value);

            this.Value = this._client.Increment(this.Key, (ulong)this.Value, (ulong)incValue);
        }

        /// <summary>
        /// Decreases value of object and refreshes with new value. Atomic function.
        /// </summary>
        /// <param name="incValue">Ammount for decrease.</param>
        public void Decrement(int decValue)
        {
            if (this.Key == null)
                throw new NullReferenceException("Key is not set yet.");

            if (this._expiresAfter.HasValue)
                this._client.Touch(this.Key, this._expiresAfter.Value);
            else if (this._expiresOn.HasValue)
                this._client.Touch(this.Key, this._expiresOn.Value);

            this.Value = this._client.Decrement(this.Key, (ulong)this.Value, (ulong)decValue);
        }

        #endregion Atomic functions.

        #region Membase object overrides.

        /// <summary>
        /// Saves current value.
        /// </summary>
        /// <param name="client">Client.</param>
        /// <returns>Returns false if save failed.</returns>
        public override bool Set()
        {
            if (this.Key == null)
                throw new NullReferenceException("Key is not set yet.");

            ulong requestedValue = this.Value;
            bool success = false;
            this.Remove();

            if (this._expiresAfter.HasValue)
                this.Value = this._client.Increment(this.Key, this.Value, 0, this._expiresAfter.Value);
            else if (this._expiresOn.HasValue)
                this.Value = this._client.Increment(this.Key, this.Value, 0, this._expiresOn.Value);
            else
                this.Value = this._client.Increment(this.Key, this.Value, 0);

            if (this.Value == requestedValue)
                success = true;

            this.JsonInSync = success;

            return success;
        }

        /// <summary>
        /// Not supported for atomic integer.
        /// </summary>
        public override bool SafeSet()
        {
            throw new NotSupportedException("SafeSet is not implementable for Atomic Integer object");
        }

        /// <summary>
        /// Adds object to Membase if it does not exist.
        /// </summary>
        /// <exception cref="MembaseException">Throws MembaseException on storage error.</exception>
        /// <returns>Returns false if object already exists.</returns>
        public override bool Add()
        {
            if (this.Key == null)
                throw new NullReferenceException("Key is not set yet.");

            ulong requestedValue = this.Value;
            bool success = false;
            if (!this.Refresh())
            {
                if (this._expiresAfter.HasValue)
                    this.Value = this._client.Increment(this.Key, this.Value, 0, this._expiresAfter.Value);
                else if (this._expiresOn.HasValue)
                    this.Value = this._client.Increment(this.Key, this.Value, 0, this._expiresOn.Value);
                else
                    this.Value = this._client.Increment(this.Key, this.Value, 0);

                if (this.Value == requestedValue)
                    success = true;
            }

            this.JsonInSync = success;

            return success;
        }

        /// <summary>
        /// Replaces value of existing object in Membase.
        /// </summary>
        /// <returns>Returns false if object does not exist.</returns>
        public override bool Replace()
        {
            if (this.Key == null)
                throw new NullReferenceException("Key is not set yet.");

            ulong requestedValue = this.Value;
            bool success = false;
            if (this.Refresh())
            {
                if (this._expiresAfter.HasValue)
                    this.Value = this._client.Increment(this.Key, this.Value, 0, this._expiresAfter.Value);
                else if (this._expiresOn.HasValue)
                    this.Value = this._client.Increment(this.Key, this.Value, 0, this._expiresOn.Value);
                else
                    this.Value = this._client.Increment(this.Key, this.Value, 0);

                if (this.Value == requestedValue)
                    success = true;
            }

            this.JsonInSync = success;

            return success;
        }

        /// <summary>
        /// Refreshes current value.
        /// </summary>
        /// <param name="withTouch">If set to true, expiery date/timespan is refreshed on items. Make sure to set the expiery date/timespan before.</param>
        public override bool Refresh(bool withTouch = false)
        {
            if (this.Key == null)
                throw new NullReferenceException("Key is not set yet.");

            object retVal;
            this.JsonInSync = this._client.TryGet(this.Key, out retVal);

            if (retVal != null)
                this.Value = ulong.Parse(retVal.ToString());
            else
                this.Value = 0;

            if (withTouch && this.JsonInSync)
            {
                if (this._expiresAfter.HasValue)
                    this._client.Touch(this.Key, this._expiresAfter.Value);
                else if (this._expiresOn.HasValue)
                    this._client.Touch(this.Key, this._expiresOn.Value);
            }

            return this.JsonInSync;
        }

        #endregion Membase object overrides.
    }
}
